﻿/*
ChangeTracking (a library for POCO change tracking).

Copyright (C) 2014 Dmitry Merzagitovich Arslanov

This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 2.1 of the License, or (at your option) any
later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace ChangeTracking
{
    /// <summary>
    /// Changes tracking manager.
    /// </summary>
    public sealed class ChangeTracker : INotifyPropertyChanged
    {
        private bool tracking;

        private Stack<ChangeSet> undos;

        private Stack<ChangeSet> redos;

        private List<string> undoHistory;

        private List<string> redoHistory;

        /// <summary>
        /// Object for which changes are tracked.
        /// </summary>
        public object TrackedObject { get; private set; }

        private bool changed;

        /// <summary>
        /// Indicates that changes are made in tracked object.
        /// </summary>
        public bool Changed
        {
            get
            {
                return changed;
            }
            set
            {
                changed = value;
                NotifyPropertyChanged("Changed");
            }
        }

        /// <summary>
        /// Indicates that user can undo changes.
        /// </summary>
        public bool CanUndo
        {
            get
            {
                return undos.Count > 0;
            }
        }

        /// <summary>
        /// Indicates that user can redo rolled back changes.
        /// </summary>
        public bool CanRedo
        {
            get
            {
                return redos.Count > 0;
            }
        }

        /// <summary>
        /// List of changes description that user can undo.
        /// </summary>
        public ReadOnlyCollection<string> UndoHistory
        {
            get
            {
                return undoHistory.AsReadOnly();
            }
        }

        /// <summary>
        /// List of changes description that user can redo.
        /// </summary>
        public ReadOnlyCollection<string> RedoHistory
        {
            get
            {
                return redoHistory.AsReadOnly();
            }
        }

        /// <summary>
        /// Create new instance of changes tracker for specified object.
        /// </summary>
        /// <param name="trackedObject">
        ///  Object for which changes are tracked.
        /// </param>
        public ChangeTracker(object trackedObject)
        {
            TrackedObject = trackedObject;
            undos = new Stack<ChangeSet>();
            redos = new Stack<ChangeSet>();
            undoHistory = new List<string>();
            redoHistory = new List<string>();
        }

        /// <summary>
        /// Start new changes tracking.
        /// </summary>
        /// <returns>Changes tracking context.</returns>
        public Transaction StartTransaction()
        {
            if (tracking) throw new InvalidOperationException();
            tracking = true;
            return new Transaction(this, TrackedObject);
        }

        internal void SubmitChanges(ChangeSet changes, string description)
        {
            Changed = true;
            tracking = false;
            undos.Push(changes);
            undoHistory.Add(description);
            redos.Clear();
            redoHistory.Clear();
            NotifyPropertyChanged("UndoHistory");
            NotifyPropertyChanged("RedoHistory");
            NotifyPropertyChanged("CanUndo");
            NotifyPropertyChanged("CanRedo");
        }

        /// <summary>
        /// Undo changes.
        /// </summary>
        public void Undo()
        {
            ChangeSet changeSet = undos.Pop();
            changeSet.Undo();
            redos.Push(changeSet);
            redoHistory.Add(undoHistory[0]);
            undoHistory.RemoveAt(0);
            Changed = true;
            NotifyPropertyChanged("UndoHistory");
            NotifyPropertyChanged("RedoHistory");
            NotifyPropertyChanged("CanUndo");
            NotifyPropertyChanged("CanRedo");
        }

        /// <summary>
        /// Redo changes.
        /// </summary>
        public void Redo()
        {
            ChangeSet changeSet = redos.Pop();
            changeSet.Redo();
            undos.Push(changeSet);
            undoHistory.Add(redoHistory[0]);
            redoHistory.RemoveAt(0);
            Changed = true;
            NotifyPropertyChanged("UndoHistory");
            NotifyPropertyChanged("RedoHistory");
            NotifyPropertyChanged("CanUndo");
            NotifyPropertyChanged("CanRedo");
        }

        /// <summary>
        /// Clear undo/redo log.
        /// </summary>
        public void ClearHistory()
        {
            undos.Clear();
            undoHistory.Clear();
            redos.Clear();
            redoHistory.Clear();
        }

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(
                    propertyName));
        }

        /// <summary>
        /// Event to handle changes of property for changes tracker.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
    }
}
